import * as fs from "fs";
import * as path from "path";
import * as request from "request";
import * as child_process from "child_process";

import log from "./log";
import { checkStatus } from "./util";
import { Config } from "../typing";
import { compile } from "./compile";

const workProcess: any = process || {};

export abstract class Service {
    readonly serviceName: string;
    public config: Config;
    
    constructor(config: Config, serviceName: string) {
        this.config = config;
        this.serviceName = serviceName;
    }

    /**
     * 启动服务
     * @param cmd 启动命令
     */
    public async start(cmd: string = "", args: string[] = []): Promise<Service> {
        if (await this.healthChecks("first")) {
            log.service.info(`检测到 ${ this.serviceName } 已启动`);
            process.exit();
            return this;
        }

        this.configService();
        
        const workerProcess = this.run(cmd, args);
        log.service.info(`${ this.serviceName } 进程pid为：${ workerProcess.pid }`);

        // 等待服务启动
        await checkStatus(() => {
            return this.healthChecks("start");
        }, 1000);
        
        log.service.info(`【${ this.serviceName }已启动！】`);

        workProcess.send({
            type: "running",
            serviceName: this.serviceName,
        });

        return this;
    }

    /**
     * 过滤日志信息
     * @param {string} data 日志
     */
    public filter(data: string) {
        const globalFilterLog = this.config.globalFilterLog || [];
        const filterLog = this.config.service[this.serviceName].filterLog || [];
        const filter = globalFilterLog.concat(filterLog).filter((item) => item !== "");
        // 未配置任何过滤规则时，默认不显示
        try {
            return filter.length > 0 && new RegExp(filter.join("|")).test(data);
        } catch (error) {
            log.service.error(error);
        }
        return false;
    }

    /**
     * 运行命令
     * @param cmd 启动命令
     * @param args 启动参数
     */
    protected abstract run(cmd: string, args: string[]): child_process.ChildProcessWithoutNullStreams;

    /**
     * 健康检查
     */
    public abstract async healthChecks(type: string): Promise<boolean>;

    /**
     * 配置服务
     */
    protected abstract configService();

    /**
     * 更新服务配置文件
     * @param configStr 新配置
     */
    public static async updateConfig(config: Config, configStr: string): Promise<boolean> {
        log.service.info("未实现updateConfig");
        return false;
    }

    /**
     * 获取服务配置文件
     * @param config 服务配置
     */
    public static async getConfig(config: Config): Promise<string> {
        log.service.info("未实现getConfig");
        return "";
    }

    /**
     * 监听配置文件变动
     * @param config 服务配置
     */
    public static monitorConfig(config: Config, callback) {
        log.service.info("未实现monitorConfig");
    }
}

export class NodeService extends Service {
    public ip?: string;
    public root?: string;
    public port?: number;

    constructor(config: Config, serviceName: string) {
        super(config, serviceName);
        if (config.service[serviceName]) {
            this.ip = config.service[serviceName].ip;
            this.root = config.service[serviceName].root;
            this.port = config.service[serviceName].port;
        }
    }

    /**
     * 运行命令
     * @param cmd 
     * @param args 
     */
    protected run(cmd: string, args: string[]) {
        const worker = child_process.spawn(cmd, args, { cwd: this.root });
        worker.stdout.on("data", (data) => {
            if (this.filter(data)) {
                log[this.serviceName].info(data);
            }
        });
        worker.stderr.on("data", (data: string) => {
            log[this.serviceName].error(data.toString());
        });
        return worker;
    }

    /**
     * 启动优化
     */
    public optimize() {
        // consul服务进行缓存
        const template = fs.readFileSync(path.resolve("template/consulCache.js.template")).toString();
        const consulIndex = path.resolve(this.root, "node_modules/appConsul/index.js");
        fs.writeFileSync(consulIndex, compile(template, this.config));

        // config自动刷新配置
        const refreshTemplate = fs.readFileSync(path.resolve("template/configRefresh.js.template")).toString();
        const configJs = path.resolve(this.root, "node_modules/appConfig/lib/config.js");
        const js = fs.readFileSync(configJs).toString();
        // 根据id判断自动刷新是否已经配置到该服务
        if (!/423c2a13-b184-42bd-8cf4-b1439fc2b288/.test(js)) {
            fs.writeFileSync(configJs, js + compile(refreshTemplate, this.config));
        }
        return this;
    }

    /**
     * 健康检查
     */
    public async healthChecks(): Promise<boolean> {
        return new Promise((resolve) => {
            const url = `http://${ this.ip }:${ this.port }/`;
            log.app.info(`${ this.serviceName } 服务启动检查: ${ url }`);
            request.get({ url }, (err, res) => {
                resolve(!err && res.statusCode == 200);
            });
        });
    }

    /**
     * 服务配置
     */
    public configService() {
        const configPath = path.resolve(this.root, "service.json");
        const serviceJson = JSON.parse(fs.readFileSync(configPath).toString());
        serviceJson.port = this.port;
        serviceJson.version_tag = this.config.version_tag;
        Object.assign(serviceJson.remote_versions, this.config.service[this.serviceName].remote_versions);
        fs.writeFileSync(configPath, JSON.stringify(serviceJson, undefined, 4));
    }
}

export class JavaService extends Service {
    public ip?: string;
    public root?: string;
    public port?: number;

    constructor(config: Config, serviceName: string) {
        super(config, serviceName);
        if (config.service[serviceName]) {
            this.ip = config.service[serviceName].ip;
            this.root = config.service[serviceName].root;
            this.port = config.service[serviceName].port;
        }
    }

    /**
     * 运行命令
     * @param cmd 
     * @param args 
     */
    protected run(cmd: string, args: string[]): child_process.ChildProcessWithoutNullStreams {
        const workerProcess = child_process.spawn(cmd, args, { cwd: this.root });
        workerProcess.stdout.on("data", (data: string) => {
            if (this.filter(data)) {
                log.service.info(`${ this.serviceName } - ${ data }`);
            }
        });
        workerProcess.stderr.on("data", (data: string) => {
            log.service.error(`${ this.serviceName } - ${ data }`);
        });
        return workProcess;
    }

    /**
     * java构建
     */
    public build(): Promise<void> {
        return new Promise((resolve) => {
            log.service.info(`编译 ${ this.serviceName } ...`);
            const ant = child_process.exec("ant", { cwd: this.root }, () => {
                resolve();
            });
            if (ant.stderr && ant.stdout) {
                ant.stdout.on("data", (data) => {
                    log[this.serviceName].info(data);
                });
                ant.stdout.on("error", (error) => {
                    log[this.serviceName].error(error);
                });
            }
        });
    }

    /**
     * 健康检查
     */
    public async healthChecks(): Promise<boolean> {
        return new Promise((resolve) => {
            const url = `http://${ this.ip }:${ this.port }/`;
            log.app.info(`${ this.serviceName } 服务启动检查: ${ url }`);
            request.get({ url }, (err, res) => {
                resolve(!err && res.statusCode == 200);
            });
        });
    }

    /**
     * 服务配置
     */
    public configService() {
        const configPath = path.resolve(this.root, "service.json");
        const serviceJson = JSON.parse(fs.readFileSync(configPath).toString());
        serviceJson.port = this.port;
        serviceJson.version_tag = this.config.version_tag;
        Object.assign(serviceJson.remote_versions, this.config.service[this.serviceName].remote_versions);
        fs.writeFileSync(configPath, JSON.stringify(serviceJson, undefined, 4));
    }
}